﻿#region License

// Another free, open solution by someone who gets it.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections.Generic;
using System.Linq;

namespace Patterns.Reflection
{
	/// <summary>
	/// 	Encapsulates a lazy-loaded collection of lexical closures that provide access to the getters of reflected member values.
	/// </summary>
	public class MemberValueRetrievers<T> : Dictionary<MemberName, Action<Action<object>, IEnumerable<object>>> where T : class
	{
		private readonly ReflectedObject<T> _reflection;

		/// <summary>
		/// 	Initializes a new instance of the <see cref = "MemberValueRetrievers&lt;T&gt;" /> class.
		/// </summary>
		/// <param name = "reflection">The reflection.</param>
		public MemberValueRetrievers(ReflectedObject<T> reflection)
		{
			_reflection = reflection;
		}

		/// <summary>
		/// 	Gets or sets the value associated with the specified key.
		/// </summary>
		/// <returns>The value associated with the specified key.</returns>
		/// <exception cref = "T:System.ArgumentNullException">key is null.</exception>
		public new Action<Action<object>, IEnumerable<object>> this[MemberName key]
		{
			get
			{
				if (!ContainsKey(key)) base[key] = GetMemberValueRetrieval(key);
				return base[key];
			}
		}

		/// <summary>
		/// Tries to get the value at the specified key.
		/// </summary>
		/// <param name="key">The key.</param>
		/// <param name="value">The value.</param>
		/// <returns>The value associated with the specified key.</returns>
		public new bool TryGetValue(MemberName key, out Action<Action<object>, IEnumerable<object>> value)
		{
			value = this[key];
			return true;
		}

		private Action<Action<object>, IEnumerable<object>> GetMemberValueRetrieval(MemberName name)
		{
			IndexedProperty indexedProperty = _reflection.GetIndexedProperty(name);
			if (indexedProperty != null && indexedProperty.CanGet)
			{
				return (action, args) =>
				{
					Func<object> getter = () => indexedProperty.Get(args.ToArray());
					action(getter());
				};
			}

			FieldOrProperty property = _reflection.GetProperty(name);
			if (property != null && property.CanGet)
			{
				return (action, args) =>
				{
					Func<object> getter = () => property.Get();
					action(getter());
				};
			}

			FieldOrProperty field = _reflection.GetField(name);
			if (field != null && field.CanGet)
			{
				return (action, args) =>
				{
					Func<object> getter = () => field.Get();
					action(getter());
				};
			}

			return null;
		}
	}
}